package com.hs.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hs.entity.dto.TrendDTO;
import com.hs.entity.pojo.*;
import com.hs.entity.vo.PageVO;
import com.hs.entity.vo.TrendSearchVO;
import com.hs.entity.vo.TrendVO;
import com.hs.entity.vo.UserTrendVO;
import com.hs.mapper.TrendMapper;
import com.hs.service.*;
import com.hs.utils.*;
import io.jsonwebtoken.Claims;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.jetbrains.annotations.NotNull;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.hs.constant.SystemConstant.DEFAULT_VIEW_PAGE_SIZE;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author hs
 * @since 2023-08-05
 */
@Service
public class TrendServiceImpl extends ServiceImpl<TrendMapper, Trend> implements TrendService {

    @Resource
    private TagService tagService;

    @Resource
    private UserService userService;

    @Resource
    private TrendLikeService trendLikeService;

    @Resource
    private TrendImageService trendImageService;

    @Resource
    private TrendCollectService trendCollectService;

    @Resource
    private ImageService imageService;

    @Resource
    private RedisCache redisCache;

    @Resource
    private RestHighLevelClient restHighLevelClient;


    @Override
    public ResultResponse<PageVO> getTrends(Integer pageNum, HttpServletRequest request) {

        Page<Trend> page = new Page<>(pageNum, DEFAULT_VIEW_PAGE_SIZE);
        query().orderByDesc("id").page(page);
        PageVO pageVO = getPageVO(page, request);
        return ResultResponse.ok(pageVO);
    }

    @NotNull
    private PageVO getPageVO(Page<Trend> page, HttpServletRequest request) {
        List<Trend> trends = page.getRecords();

        String token = request.getHeader("token");

        // 拿到redis中的动态评论与动态点赞的map
        Map<String, Integer> trendCommentCountMap = redisCache.getCacheMap("pet:view:trendCommentCount:");
        Map<String, Integer> trendLikeCountMap = redisCache.getCacheMap("pet:view:trendLikeCount:");


        List<TrendVO> trendVOS = BeanCopyUtil.copyBeanList(trends, TrendVO.class);
        trendVOS.forEach(trendVO -> {

            // 动态ID
            Integer trendId = trendVO.getId();

            List<Integer> imageIds = trendImageService.query()
                    .eq("trend_id", trendId)
                    .page(new Page<>(1, 3))
                    .getRecords().stream()
                    .map(TrendImage::getImageId)
                    .collect(Collectors.toList());

            if (!imageIds.isEmpty()) {
                List<String> urls = imageService.query()
                        .in("id", imageIds)
                        .list().stream()
                        .map(Image::getUrl)
                        .collect(Collectors.toList());
                trendVO.setImages(urls);
            } else {
                trendVO.setImages(Collections.emptyList());
            }


            // 查询标签
            Integer tagId = trendVO.getTagId();
            String tagName = Optional.ofNullable(OptUtil.of(tagService.getById(tagId)).get(Tag::getName).get()).orElse("");

            // 查询用户
            Integer userId = trendVO.getUserId();
            User user = Optional.ofNullable(userService.getById(userId)).orElseGet(User::new);
            trendVO.setNickName(user.getNickName());
            trendVO.setArea(user.getArea());
            trendVO.setHead(user.getHead());

            // 查询当前用户是否已点赞动态
            if (StringUtils.hasText(token)) {
                Claims claims = null;
                try {
                    claims = JwtUtil.parseJWT(token);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (Objects.nonNull(claims)) {
                    String subject = claims.getSubject();
                    Integer currentUserId = Integer.valueOf(subject);
                    LambdaQueryWrapper<TrendLike> queryWrapper = new LambdaQueryWrapper<>();
                    queryWrapper.eq(TrendLike::getUserId, currentUserId).eq(TrendLike::getTrendId, trendId);
                    // >0说明当前用户已点赞
                    trendVO.setIsLike(trendLikeService.count(queryWrapper) > 0);
                }
            }

            // 直接去redis中拿到评论数、点赞数
            trendVO.setTagName(tagName);
            trendVO.setCommentCount(trendCommentCountMap.getOrDefault(trendId.toString(), 0));
            trendVO.setLikeCount(trendLikeCountMap.getOrDefault(trendId.toString(), 0));
        });

        PageVO pageVO = new PageVO(trendVOS, page.getTotal());
        return pageVO;
    }

    @Override
    @Transactional
    public ResultResponse<?> publishTrend(TrendDTO trendDTO) {
        // 拿到用户ID
        Integer userId = SecurityUserHolder.getUserId();
        Trend trend = BeanCopyUtil.copyBean(trendDTO, Trend.class);
        List<String> images = trendDTO.getImages();
        trend.setUserId(userId);
        save(trend);
        Integer trendId = trend.getId();
        List<Image> imageList = images.stream().map(url -> new Image(null, url)).collect(Collectors.toList());
        imageService.saveBatch(imageList);
        List<TrendImage> trendImages = imageList.stream().map(Image::getId)
                .map(imageId -> new TrendImage(trendId, imageId))
                .collect(Collectors.toList());
        trendImageService.saveBatch(trendImages);

        // 更新redis中的数据
        Map<String, Integer> trendCommentCountMap = redisCache.getCacheMap("pet:view:trendCommentCount:");
        Map<String, Integer> trendLikeCountMap = redisCache.getCacheMap("pet:view:trendLikeCount:");
        Map<String, Integer> trendCollectCountMap = redisCache.getCacheMap("pet:view:trendCollectCount:");
        trendCommentCountMap.put(trendId.toString(), 0);
        trendLikeCountMap.put(trendId.toString(), 0);
        trendCollectCountMap.put(trendId.toString(), 0);
        redisCache.setCacheMap("pet:view:trendCommentCount:", trendCommentCountMap);
        redisCache.setCacheMap("pet:view:trendLikeCount:", trendLikeCountMap);
        redisCache.setCacheMap("pet:view:trendCollectCount:", trendCollectCountMap);

        return ResultResponse.ok();
    }

    @Override
    public ResultResponse<?> likeTrend(Integer trendId, Boolean flag) {
        // 先拿到用户
        Integer userId = SecurityUserHolder.getUserId();

        if (!flag) {
            // 说明动态原先是未点赞的状态
            try {
                trendLikeService.save(new TrendLike(userId, trendId));
            } catch (DuplicateKeyException e) {
                return ResultResponse.fail("请勿重复点赞！");
            }
            // redis中点赞数+1
            redisCache.incrementCacheMapValue("pet:view:trendLikeCount:", trendId.toString(), 1);
        } else {
            // 说明动态原先是已点赞的状态
            LambdaQueryWrapper<TrendLike> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(TrendLike::getTrendId, trendId).eq(TrendLike::getUserId, userId);
            trendLikeService.remove(queryWrapper);
            redisCache.incrementCacheMapValue("pet:view:trendLikeCount:", trendId.toString(), -1);
        }
        return ResultResponse.ok();
    }


    @Override
    public ResultResponse<List<UserTrendVO>> getMyLikeTrends() {
        Integer userId = SecurityUserHolder.getUserId();
        List<Integer> trendIds = trendLikeService.query()
                .eq("user_id", userId).list()
                .stream().map(TrendLike::getTrendId)
                .collect(Collectors.toList());

        List<UserTrendVO> userTrendVOS = getUserTrendVOS(trendIds);
        return ResultResponse.ok(userTrendVOS);
    }

    @NotNull
    private List<UserTrendVO> getUserTrendVOS(List<Integer> trendIds) {
        List<Trend> trends = query().in("id", trendIds).list();

        List<UserTrendVO> userTrendVOS = BeanCopyUtil.copyBeanList(trends, UserTrendVO.class);

        userTrendVOS.forEach(userTrendVO -> {
            Integer uId = userTrendVO.getUserId();
            Integer tagId = userTrendVO.getTagId();
            User user = Optional.ofNullable(userService.getById(uId)).orElseGet(User::new);
            Tag tag = Optional.ofNullable(tagService.getById(tagId)).orElseGet(Tag::new);
            userTrendVO.setNickName(user.getNickName());
            userTrendVO.setHead(user.getHead());
            userTrendVO.setTagName(tag.getName());
        });
        return userTrendVOS;
    }

    @Override
    public ResultResponse<List<UserTrendVO>> getMyCollectTrends() {
        Integer userId = SecurityUserHolder.getUserId();
        List<Integer> trendIds = trendCollectService.query().eq("user_id", userId)
                .list().stream()
                .map(TrendCollect::getTrendId)
                .collect(Collectors.toList());
        List<UserTrendVO> userTrendVOS = getUserTrendVOS(trendIds);
        return ResultResponse.ok(userTrendVOS);
    }

    @Override
    public ResultResponse<PageVO> getTrendsByTagId(Integer pageNum, Integer tagId) {
        Page<Trend> page = new Page<>(pageNum, DEFAULT_VIEW_PAGE_SIZE);
        query().eq("tag_id", tagId).orderByDesc("id").page(page);
        PageVO pageVO = getPageVO(page, null);
        return ResultResponse.ok(pageVO);
    }

    @Override
    public ResultResponse<?> collectTrend(Integer trendId, Boolean flag) {
        // 先拿到用户
        Integer userId = SecurityUserHolder.getUserId();

        if (!flag) {
            // 说明动态原先是未收藏的状态
            try {
                trendCollectService.save(new TrendCollect(userId,trendId));
            } catch (DuplicateKeyException e) {
                return ResultResponse.fail("请勿重复收藏！");
            }
            // redis中收藏数+1
            redisCache.incrementCacheMapValue("pet:view:trendCollectCount:", trendId.toString(), 1);
        } else {
            // 说明动态原先是已收藏的状态
            LambdaQueryWrapper<TrendCollect> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(TrendCollect::getTrendId, trendId).eq(TrendCollect::getUserId, userId);
            trendCollectService.remove(queryWrapper);
            redisCache.incrementCacheMapValue("pet:view:trendCollectCount:", trendId.toString(), -1);
        }
        return ResultResponse.ok();
    }

    @Override
    public ResultResponse<List<Map<String, Object>>> searchTrendsHighlight(String keyword, Integer pageIndex, Integer pageSize) throws IOException {
        // 我的傻逼逻辑是这样的

        // 先去数据库把所有动态查出来
        List<Trend> trends = query().list();

        // 然后把查出来的数据封装成VO存入elasticsearch中（批量）
        List<TrendSearchVO> trendSearchVOS = BeanCopyUtil.copyBeanList(trends, TrendSearchVO.class);
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");

        // 批量请求处理
        for (TrendSearchVO trendSearchVO : trendSearchVOS) {
            bulkRequest.add(
                    // 这里是数据信息
                    new IndexRequest("trend")
                            .id(trendSearchVO.getId().toString()) // 没有设置id 会自定生成一个随机id
                            .source(JSON.toJSONString(trendSearchVO), XContentType.JSON)
            );
        }
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);

        // 然后再去es中查询数据
        List<Map<String, Object>> searchTrends = new ArrayList<>();
        if (!bulk.hasFailures()){
            searchTrends = highlightSearch(keyword, pageIndex, pageSize);
        }

        // 最后放回给前端
        return ResultResponse.ok(searchTrends);
    }

    private List<Map<String, Object>> highlightSearch(String keyword, Integer pageIndex, Integer pageSize) throws IOException{
        SearchRequest searchRequest = new SearchRequest("trend");
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        // 匹配查询
        MatchQueryBuilder matchQueryBuilder = QueryBuilders.matchQuery("sketch",keyword);
        searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchSourceBuilder.query(matchQueryBuilder);

        // 分页
        searchSourceBuilder.from(pageIndex);
        searchSourceBuilder.size(pageSize);
        // 高亮
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("sketch");
        highlightBuilder.preTags("<span style='color:red'>");
        highlightBuilder.postTags("</span>");
        searchSourceBuilder.highlighter(highlightBuilder);
        // 执行查询
        searchRequest.source(searchSourceBuilder);
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 解析结果
        SearchHits hits = searchResponse.getHits();
        List<Map<String, Object>> searchTrends = new ArrayList<>();
        for (SearchHit documentFields : hits.getHits()) {
            // 使用新的字段值（高亮），覆盖旧的字段值
            Map<String, Object> sourceAsMap = documentFields.getSourceAsMap();
            // 高亮字段
            Map<String, HighlightField> highlightFields = documentFields.getHighlightFields();
            HighlightField name = highlightFields.get("sketch");
            // 替换
            if (name != null){
                Text[] fragments = name.fragments();
                StringBuilder new_name = new StringBuilder();
                for (Text text : fragments) {
                    new_name.append(text);
                }
                sourceAsMap.put("sketch",new_name.toString());
            }
            searchTrends.add(sourceAsMap);
        }
        return searchTrends;
    }

}
